#include <windows.h>

#include <string.h>

#include "lib.h"
#include "dir.h"
#include "pathname.h"

const String::Character Directory::sep = '\\';

bool8 
Directory::getCurrent(PathName& Result)
{
	uint16 buf[MAX_PATH];

	if (0 != GetCurrentDirectory(MAX_PATH, buf) &&
		Result.set(buf))
		return true;

	Result.makeEmpty();
	return false;
}

bool8
Directory::make(const PathName& Path)
{
	uint32 attr;

	if (Path.getLen())
	{
		attr = GetFileAttributes(Path);

		if (attr == 0xffffffffL ||
			((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
		{
			if (!CreateDirectory(Path, (LPSECURITY_ATTRIBUTES)0))
				return false;
		}
	}
	return true;
}

bool8
Directory::makeAll(const PathName& Path, EndOpt End)
{
	uint16 buf[MAX_PATH], *p;
	int32 len;
	uint32 attr;

	wcscpy(buf, Path);
	len = Path.getLen();

	if (End == EndIsDirectory && !Path.endsWith(sep))
	{
		buf[len++] = sep;
		buf[len] = '\0';
	}

	// Check for UNC paths.
	if (buf[0] == sep && buf[1] == sep)
	{
		// Skip the machine name.
		p = wcschr(buf + 2, sep);

		// Skip the share name.
		if (p)
		{
			p = wcschr(p + 1, sep);
			
			// Advance to first file/directory.
			if (p)
				p = wcschr(p + 1, sep);
		}
	}
	else
		p = wcschr(buf, sep);

	while (p)
	{
		if (p != buf)
		{
			*p = '\0';
			attr = GetFileAttributes(buf);
			if (attr == 0xffffffffL ||
				((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
			{
				if (!CreateDirectory(buf, (LPSECURITY_ATTRIBUTES)0))
					return false;
			}

			*p = sep;
		}
		p = wcschr(p + 1, sep);
	}

	return true;
}

bool8
Directory::remove(const PathName& Path)
{
	uint32 attr = GetFileAttributes(Path);

	if (attr == 0xffffffffL)
		return true;

	if (attr & FILE_ATTRIBUTE_DIRECTORY)
		return (bool8)RemoveDirectory(Path);

	return (bool8)DeleteFile(Path);
}

bool8
Directory::removeAll(const PathName& Path)
{
	HANDLE hFind;
	WIN32_FIND_DATA wfd;
	PathName sub;
	bool8 more;
	int32 len;

	len = Path.getLen();

	if (len <= 0)
		return true;

	wcscpy(wfd.cFileName, Path);

	if (!Path.endsWith(sep))
		wfd.cFileName[len++] = sep;
	wfd.cFileName[len++] = '*';
	wfd.cFileName[len++] = '.';
	wfd.cFileName[len++] = '*';
	wfd.cFileName[len++] = '\0';

	hFind = FindFirstFile(wfd.cFileName, &wfd);
	more = (bool8)(hFind != INVALID_HANDLE_VALUE);

	while (more)
	{
		if (wfd.cFileName[0] == '.')
		{
			if (wfd.cFileName[1] == '\0' ||
				wfd.cFileName[1] == '.' && wfd.cFileName[2] == '\0')
			{
				more = (bool8)(FindNextFile(hFind, &wfd) != 0);
				continue;
			}
		}

		if (sub.set(Path) && sub.concat(wfd.cFileName))
		{
			if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				if (!removeAll(sub))
				{
					FindClose(hFind);
					return false;
				}
			}
			else
			{
				if (!DeleteFile(sub))
				{
					FindClose(hFind);
					return false;
				}
			}
		}
		else
		{
			FindClose(hFind);
			return false;
		}

		more = (bool8)(FindNextFile(hFind, &wfd) != 0);
	}

	if (hFind != INVALID_HANDLE_VALUE)
	{
		bool8 err = (bool8)(GetLastError() != ERROR_NO_MORE_FILES);  

		FindClose(hFind);

		if (err)
			return false;
	}

	// directory is empty (or non-existent)
	//
	return remove(Path);
}

bool8
Directory::move(const PathName& Src, const PathName& Dest)
{
	// fails across different file volumes
	//

	uint32 sattr, dattr;

	sattr = GetFileAttributes(Src);
	if (sattr == 0xffffffffL)
		return false;

	dattr = GetFileAttributes(Dest);

	if (sattr & FILE_ATTRIBUTE_DIRECTORY)
	{
		if (dattr == 0xffffffffL ||
			((dattr & FILE_ATTRIBUTE_DIRECTORY) == 0))
		{
			if (!makeAll(Dest))
				return false;
		}
	}
	else
	{
		if (!makeAll(Dest, EndIsFilename))
			return false;

		if (dattr == 0xffffffffL)
		{
			if (!MoveFile(Src, Dest))
				return false;
		}
		else if ((dattr & FILE_ATTRIBUTE_DIRECTORY) == 0)
		{
			if (!CopyFile(Src, Dest, FALSE))
				return false;
						
			if (!DeleteFile(Src))
				return false;
		}
		else
			return false;
	}
	return true;
}


bool8
Directory::copy(const PathName& Src, const PathName& Dest)
{
	// fails across different file volumes
	//

	uint32 sattr, dattr;

	sattr = GetFileAttributes(Src);
	if (sattr == 0xffffffffL)
		return false;

	dattr = GetFileAttributes(Dest);

	if (sattr & FILE_ATTRIBUTE_DIRECTORY)
	{
		if (dattr == 0xffffffffL ||
			((dattr & FILE_ATTRIBUTE_DIRECTORY) == 0))
		{
			if (!makeAll(Dest))
				return false;
		}
	}
	else
	{
		if (!makeAll(Dest, EndIsFilename))
			return false;

		if (dattr == 0xffffffffL)
		{
			if (!CopyFile(Src, Dest, FALSE))
				return false;
		}
		else if ((dattr & FILE_ATTRIBUTE_DIRECTORY) == 0)
		{
			if (!CopyFile(Src, Dest, FALSE))
				return false;
		}
		else
			return false;
	}
	return true;
}


bool8
Directory::copyContents(const PathName& Src, const PathName& Dest, int32* errCode)
{
	// fails across different file volumes
	//

	if (Src.getLen() && Dest.getLen())
	{
		uint32 attr;
		HANDLE hFind;
		WIN32_FIND_DATA wfd;
		PathName subsrc, subdest;
		bool8 more;
		int32 len;

		attr = GetFileAttributes(Src);
		if (attr == 0xffffffffL ||
			((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
			return false;

		attr = GetFileAttributes(Dest);
		if (attr == 0xffffffffL ||
			((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
			return false;

		len = Src.getLen();

		wcscpy(wfd.cFileName, Src);

		if (!Src.endsWith(sep))
			wfd.cFileName[len++] = sep;
		wfd.cFileName[len++] = '*';
		wfd.cFileName[len++] = '.';
		wfd.cFileName[len++] = '*';
		wfd.cFileName[len++] = '\0';

		hFind = FindFirstFile(wfd.cFileName, &wfd);
		more = (bool8)(hFind != INVALID_HANDLE_VALUE);

		while (more)
		{
			if (wfd.cFileName[0] == '.')
			{
				if (wfd.cFileName[1] == '\0' ||
					wfd.cFileName[1] == '.' && wfd.cFileName[2] == '\0')
				{
					more = (bool8)(FindNextFile(hFind, &wfd) != 0);
					continue;
				}
			}

			if (subsrc.set(Src) && subsrc.concat(wfd.cFileName) &&
				subdest.set(Dest) && subdest.concat(wfd.cFileName))
			{
				attr = GetFileAttributes(subdest);

				if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					if (attr == 0xffffffffL ||
						((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
					{
						if (!make(subdest))
							goto abort;
					}

					if (!copyContents(subsrc, subdest, errCode))
						goto abort;
				}
				else
				{
					if (attr == 0xffffffffL)
					{
						if (!CopyFile(subsrc, subdest, FALSE))
							goto abort;
					}
					else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)
					{
						if (!CopyFile(subsrc, subdest, FALSE))
							goto abort;
					}
					else
						goto abort;
				}
			}
			else
				goto abort;

			more = (bool8)(FindNextFile(hFind, &wfd) != 0);
		}

		if (hFind != INVALID_HANDLE_VALUE)
		{
			bool8 err = (bool8)(GetLastError() != ERROR_NO_MORE_FILES);  

			FindClose(hFind);

			if (err)
				return false;
		}

		if (errCode)
			*errCode=0;

		return true;

abort:
		if (hFind != INVALID_HANDLE_VALUE)
			FindClose(hFind);
	}

	if (errCode)
		*errCode = GetLastError();

	return false;
}

bool8
Directory::moveContents(const PathName& Src, const PathName& Dest, int32* errCode)
{
	// fails across different file volumes
	//

	if (Src.getLen() && Dest.getLen())
	{
		uint32 attr;
		HANDLE hFind;
		WIN32_FIND_DATA wfd;
		PathName subsrc, subdest;
		bool8 more;
		int32 len;

		attr = GetFileAttributes(Src);
		if (attr == 0xffffffffL ||
			((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
			return false;

		attr = GetFileAttributes(Dest);
		if (attr == 0xffffffffL ||
			((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
			return false;

		len = Src.getLen();

		wcscpy(wfd.cFileName, Src);

		if (!Src.endsWith(sep))
			wfd.cFileName[len++] = sep;
		wfd.cFileName[len++] = '*';
		wfd.cFileName[len++] = '.';
		wfd.cFileName[len++] = '*';
		wfd.cFileName[len++] = '\0';

		hFind = FindFirstFile(wfd.cFileName, &wfd);
		more = (bool8)(hFind != INVALID_HANDLE_VALUE);

		while (more)
		{
			if (wfd.cFileName[0] == '.')
			{
				if (wfd.cFileName[1] == '\0' ||
					wfd.cFileName[1] == '.' && wfd.cFileName[2] == '\0')
				{
					more = (bool8)(FindNextFile(hFind, &wfd) != 0);
					continue;
				}
			}

			if (subsrc.set(Src) && subsrc.concat(wfd.cFileName) &&
				subdest.set(Dest) && subdest.concat(wfd.cFileName))
			{
				attr = GetFileAttributes(subdest);

				if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					if (attr == 0xffffffffL ||
						((attr & FILE_ATTRIBUTE_DIRECTORY) == 0))
					{
						if (!make(subdest))
							goto abort;
					}

					if (!moveContents(subsrc, subdest, errCode))
						goto abort;
				}
				else
				{
					if (attr == 0xffffffffL)
					{
						if (!MoveFile(subsrc, subdest))
							goto abort;
					}
					else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)
					{
						if (!CopyFile(subsrc, subdest, FALSE))
							goto abort;
						
						if (!DeleteFile(subsrc))
							goto abort;
					}
					else
						goto abort;
				}
			}
			else
				goto abort;

			more = (bool8)(FindNextFile(hFind, &wfd) != 0);
		}

		if (hFind != INVALID_HANDLE_VALUE)
		{
			bool8 err = (bool8)(GetLastError() != ERROR_NO_MORE_FILES);  

			FindClose(hFind);

			if (err)
				return false;
		}

		if (errCode)
			*errCode=0;

		return true;

abort:
		if (hFind != INVALID_HANDLE_VALUE)
			FindClose(hFind);
	}

	if (errCode)
		*errCode = GetLastError();

	return false;
}


bool8
Directory::isDirectory(const PathName &Src)
{
	uint32 dattr = GetFileAttributes(Src);
	if (dattr == 0xffffffffL)
		return false;

	if (dattr & FILE_ATTRIBUTE_DIRECTORY)
		return true;

	return false;
}
